﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using Xceed.Silverlight.Data.Stats;

namespace Xceed.Silverlight.Data
{
  public abstract class AsyncSqlDataSourceProvider : AsyncDataSourceProvider
  {
    #region Static Fields

    private static readonly Guid ExecuteStatQueryCallId = DataUtils.CreateCallId();

    #endregion

    #region Constructor

    protected AsyncSqlDataSourceProvider()
    {
    }

    #endregion

    #region CanSkip Property

    public override bool CanSkip
    {
      get
      {
        return true;
      }
    }

    #endregion

    #region CanFilter Property

    public sealed override bool CanFilter
    {
      get
      {
        return true;
      }
    }

    #endregion

    #region CanFilterGroup Property

    public sealed override bool CanFilterGroup
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanJump Property

    public sealed override bool CanJump
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanJumpToItem Property

    public sealed override bool CanJumpToItem
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanCount Property

    public override bool CanCount
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanGroupCount Property

    public sealed override bool CanGroupCount
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanCalculateStats Property

    public override bool CanCalculateStats
    {
      get
      {
        return this.CanCount;
      }
    }

    #endregion

    #region CanFilterPredicate Internal Property

    internal override bool CanFilterPredicate
    {
      get
      {
        return false;
      }
    }

    #endregion

    protected abstract SqlFilterExpressionFormatter GetSqlQueryBuilder( FilterExpressionBase expression );

    protected abstract IAsyncResult BeginCount(
      SqlFilterExpressionResult filter,
      AsyncCallback callback,
      object state );

    protected abstract CountQueryResult EndCount( IAsyncResult asyncResult );

    protected abstract IAsyncResult BeginGetData(
      int take,
      long skip,
      IEnumerable<SortDescription> sortOrder,
      SqlFilterExpressionResult filter,
      AsyncCallback callback,
      object state );

    protected abstract DataQueryResult EndGetData( IAsyncResult asyncResult );

    protected abstract IAsyncResult BeginGetStatResult(
      StatFunction statFunction,
      SqlFilterExpressionResult filter,
      AsyncCallback callback,
      object state );

    protected abstract StatQueryResult EndGetStatResult( IAsyncResult asyncResult );

    protected sealed override IAsyncResult BeginExecuteCountQuery(
      CountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      SqlFilterExpressionResult filter = this.GetSqlFilterExpression( queryParameters.Filter );

      return this.BeginCount( filter, callback, state );
    }

    protected sealed override CountQueryResult EndExecuteCountQuery( IAsyncResult asyncResult )
    {
      return this.EndCount( asyncResult );
    }

    protected sealed override IAsyncResult BeginExecuteDataQuery(
      DataQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      IEnumerable<SortDescription> sortOrder = ( queryParameters.Sort != null )
                                                 ? queryParameters.Sort.SortDescriptions
                                                 : Enumerable.Empty<SortDescription>();
      long skip = ( queryParameters.Skip != null ) ? queryParameters.Skip.Count : 0;
      int take = ( queryParameters.Take != null ) ? queryParameters.Take.Count : 0;
      SqlFilterExpressionResult filter = this.GetSqlFilterExpression( queryParameters.Filter );

      return this.BeginGetData( take, skip, sortOrder, filter, callback, state );
    }

    protected sealed override DataQueryResult EndExecuteDataQuery( IAsyncResult asyncResult )
    {
      return this.EndGetData( asyncResult );
    }

    protected sealed override IAsyncResult BeginExecuteGroupCountQuery(
      GroupCountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected sealed override GroupCountQueryResult EndExecuteGroupCountQuery( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected sealed override IAsyncResult BeginExecuteStatQuery(
      StatQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      StatFunction statFunction = queryParameters.Function.StatFunction;
      CountFunction countFunction = queryParameters.Function.StatFunction as CountFunction;
      FilterRequest filterRequest = queryParameters.Filter;
      SqlFilterExpressionResult filter;

      // Let the derived class handled the StatFunction.
      if( ( countFunction == null ) || ( !this.CanCount ) )
      {
        filter = this.GetSqlFilterExpression( filterRequest );

        return this.BeginGetStatResult( statFunction, filter, callback, state );
      }

      // The CountFunction may be handled by a call to BeginCount.
      FilterExpressionBase filterExpression = filterRequest.Expression;
      string propertyName = countFunction.GetSourcePropertyNames().Single();
      bool isNullableType = this.GetElementMemberType( propertyName ).IsNullableType();

      // Modify the filter expression to take care of the null value.
      if( isNullableType )
      {
        if( countFunction.IncludeNullValues )
        {
          FilterExpression includeNull = new FilterExpression( propertyName, FilterOperator.Equal, null );

          if( filterExpression != null )
          {
            filterExpression = new OrFilterExpression( includeNull, filterExpression );
          }
          else
          {
            filterExpression = includeNull;
          }
        }
        else
        {
          FilterExpression excludeNull = new FilterExpression( propertyName, FilterOperator.NotEqual, null );

          if( filterExpression != null )
          {
            filterExpression = new AndFilterExpression( excludeNull, filterExpression );
          }
          else
          {
            filterExpression = excludeNull;
          }
        }
      }

      filter = this.GetSqlFilterExpression( filterExpression );

      AsyncResult asyncResult = new AsyncResult(
                                  AsyncSqlDataSourceProvider.ExecuteStatQueryCallId,
                                  callback,
                                  state );
      asyncResult.Start(
        ( cb, st ) => this.BeginCount( filter, cb, st ),
        null );

      return asyncResult;
    }

    protected sealed override StatQueryResult EndExecuteStatQuery( IAsyncResult asyncResult )
    {
      if( asyncResult.UseCallId( AsyncSqlDataSourceProvider.ExecuteStatQueryCallId ) )
      {
        CountQueryResult countQueryResult = this.EndCount( asyncResult.GetLastAsyncResult<IAsyncResult>() );
        StatResult statResult = new StatResult( countQueryResult.Count );

        return new StatQueryResult( statResult );
      }
      else
      {
        return this.EndGetStatResult( asyncResult );
      }
    }

    protected override IEnumerable<Type> GetSupportedStatFunctions( Type dataType )
    {
      // Check if we can replace the CountFunction with a call to BeginCount.
      if( this.CanCount )
      {
        bool mayCount = !dataType.IsNullableType();
        if( ( !mayCount ) && ( this.CanFilter ) )
        {
          // Since the CountFunction may require null values inclusion or exclusion, we need
          // to be able set a filter for null or non-null values.
          List<FilterOperator> filterOperators = this.GetSupportedFilterOperators( dataType ).ToList();

          mayCount = ( filterOperators.Contains( FilterOperator.Equal ) )
                  && ( filterOperators.Contains( FilterOperator.NotEqual ) );
        }

        if( mayCount )
          yield return typeof( CountFunction );
      }

      yield break;
    }

    private SqlFilterExpressionResult GetSqlFilterExpression( FilterRequest request )
    {
      if( request == null )
        return null;

      return this.GetSqlFilterExpression( request.Expression );
    }

    private SqlFilterExpressionResult GetSqlFilterExpression( FilterExpressionBase expression )
    {
      if( expression == null )
        return null;

      SqlFilterExpressionFormatter converter = this.GetSqlQueryBuilder( expression );
      Debug.Assert( converter != null );

      return converter.GetResult();
    }
  }
}
